﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using NUnit.Framework;
using Tangosol.Net;
using Tangosol.Net.Cache;
using NUnit.Framework.SyntaxHelpers;
using Tangosol.Util.Aggregator;
using Tangosol.Util.Filter;
using Coherence.Contrib;

namespace Coherence.Linq.UnitTests
{
    [TestFixture]
    public class LinqUnittests
    {
        [SetUp]
        public void Init()
        {
            INamedCache PersonCache = CacheFactory.GetCache("dist-Person");
            INamedCache PhoneCache = CacheFactory.GetCache("dist-Phone");
            INamedCache PersonCache2 = CacheFactory.GetCache("dist-Person2");
            INamedCache PhoneCache2 = CacheFactory.GetCache("dist-Phone2");
            PersonCache.Destroy();
            PhoneCache.Destroy();
            PersonCache2.Destroy();
            PhoneCache2.Destroy();
        }

        [TearDown]
        public void TearDown()
        {
            INamedCache PersonCache = CacheFactory.GetCache("dist-Person");
            INamedCache PhoneCache = CacheFactory.GetCache("dist-Phone");
            INamedCache PersonCache2 = CacheFactory.GetCache("dist-Person2");
            INamedCache PhoneCache2 = CacheFactory.GetCache("dist-Phone2");
            PersonCache.Destroy();
            PhoneCache.Destroy();
            PersonCache2.Destroy();
            PhoneCache2.Destroy();
        }

        [Test]
        public void AddData()
        {
            System.Random rnd = new Random(0);
            INamedCache PersonCache = CacheFactory.GetCache("dist-Person");
            INamedCache PhoneCache = CacheFactory.GetCache("dist-Phone");
            Dictionary<int, Person> perDict = new Dictionary<int, Person>();
            Dictionary<string, PhoneNumber> phonDict = new Dictionary<string, PhoneNumber>();
            for (int i = 0; i < 1000; i++)
            {
                perDict.Add(i, new Person()
                {
                    ID = i,
                    FirstName = string.Format("First Name {0}", i),
                    LastName = string.Format("LastName {0}", i),
                    Address = string.Format("Address {0}", i),
                    Title = i % 2 == 1 ? "Mr" : "Mrs"
                });
                for (int j = 0; j < (i % 3) + 1; j++)
                {
                    phonDict.Add(string.Format("{0}_{1}", i, j), new PhoneNumber()
                    {
                        ID = j,
                        PersonID = i,
                        Number = "Phone " + j.ToString(),
                        Name = "Name " + j.ToString()
                    });
                }
            }

            Assert.That(0, Is.EqualTo(PersonCache.Aggregate(new AlwaysFilter(), new Count())), "Person Cache Not Empty");
            Assert.That(0, Is.EqualTo(PhoneCache.Aggregate(new AlwaysFilter(), new Count())), "Phone Cache Not Empty");
            PersonCache.InsertAll(perDict);
            PhoneCache.InsertAll(phonDict);
            Assert.That(1000, Is.EqualTo(PersonCache.Aggregate(new AlwaysFilter(), new Count())), "Person Cache Count Not Valid");
            Assert.That(1999, Is.EqualTo(PhoneCache.Aggregate(new AlwaysFilter(), new Count())), "Phone Cache Count Not Valid");
        }

        [Test]
        public void AddData2()
        {
            System.Random rnd = new Random(0);
            INamedCache PersonCache = CacheFactory.GetCache("dist-Person2");
            INamedCache PhoneCache = CacheFactory.GetCache("dist-Phone2");
            Dictionary<int, Person2> perDict = new Dictionary<int, Person2>();
            Dictionary<string, PhoneNumber> phonDict = new Dictionary<string, PhoneNumber>();
            for (int i = 0; i < 1000; i++)
            {
                perDict.Add(i, new Person2()
                {
                    ID = i,
                    FirstName = string.Format("First Name {0}", i),
                    LastName = string.Format("LastName {0}", i),
                    Address = string.Format("Address {0}", i),
                    Title = i % 2 == 1 ? "Mr" : "Mrs"
                });
                for (int j = 0; j < (i % 3) + 1; j++)
                {
                    phonDict.Add(string.Format("{0}_{1}", i, j), new PhoneNumber()
                    {
                        ID = j,
                        PersonID = i,
                        Number = "Phone " + j.ToString(),
                        Name = "Name " + j.ToString()
                    });
                }
            }

            Assert.That(0, Is.EqualTo(PersonCache.Aggregate(new AlwaysFilter(), new Count())), "Person Cache Not Empty");
            Assert.That(0, Is.EqualTo(PhoneCache.Aggregate(new AlwaysFilter(), new Count())), "Phone Cache Not Empty");
            PersonCache.InsertAll(perDict);
            PhoneCache.InsertAll(phonDict);
            Assert.That(1000, Is.EqualTo(PersonCache.Aggregate(new AlwaysFilter(), new Count())), "Person Cache Count Not Valid");
            Assert.That(1999, Is.EqualTo(PhoneCache.Aggregate(new AlwaysFilter(), new Count())), "Phone Cache Count Not Valid");
        }
        
        #region Person Tests
        [Test]
        public void SimplePersonQuery()
        {
            AddData();

            INamedCache PersonCache = CacheFactory.GetCache("dist-Person");
            Dictionary<int, Person> entries = PersonCache.Entries.OfType<ICacheEntry>().ToDictionary<ICacheEntry, int, Person>(e => (int)e.Key, e => (Person)e.Value);

            Assert.That(entries, Has.Count(1000), "Invalid number of entries");
            foreach (int key in entries.Keys)
            {
                Person p = entries[key];
                CheckFirstName(key, p.FirstName);
                CheckLastName(key, p.LastName);
                CheckAddress(key, p.Address);
                CheckTitle(key, p.Title);
                CheckId(key, p.ID);
            }
        }

        [Test]
        public void Linq_SelectPersonNoFilter()
        {
            AddData();

            CoherenceQuery<Person> coherenceData =
              new CoherenceQuery<Person>(
                  new CoherenceQueryProvider(CacheFactory.GetCache("dist-Person")));

            var people = from person in coherenceData
                         select person;

            Person[] Result = people.ToArray();
            Assert.That(Result, Has.Length(1000));
            foreach (Person person in Result)
            {
                CheckFirstName(person.ID, person.FirstName);
                CheckLastName(person.ID, person.LastName);
                CheckAddress(person.ID, person.Address);
                CheckTitle(person.ID, person.Title);
            }
        }

        [Test]
        public void Linq_SelectPersonSimpleFilter()
        {
            AddData();

            CoherenceQuery<Person> coherenceData =
              new CoherenceQuery<Person>(
                  new CoherenceQueryProvider(CacheFactory.GetCache("dist-Person")));

            var people = from person in coherenceData
                         where person.Title == "Mr"
                         select person;

            Person[] Result = people.ToArray();
            Assert.That(Result, Has.Length(500));
            foreach (Person person in Result)
            {
                Assert.That(person.Title, Is.EqualTo("Mr"));
            }
        }

        [Test]
        public void Linq_SelectPersonCombinedFilter()
        {
            AddData();

            CoherenceQuery<Person> coherenceData =
              new CoherenceQuery<Person>(
                  new CoherenceQueryProvider(CacheFactory.GetCache("dist-Person")));

            var people = from person in coherenceData
                         where person.Title == "Mr"
                         && person.FirstName.Like("First Name 10%")
                         select person;

            Person[] Result = people.ToArray();
            Assert.That(Result, Has.Length(5));
            
            foreach (Person person in Result)
            {
                Assert.That(person.Title, Text.Matches("Mr"));
                Assert.That(person.FirstName, Text.StartsWith("First Name 10"));
            }
        }

        [Test]
        public void Linq_SelectPersonCombinedInFilter()
        {
            AddData();

            CoherenceQuery<Person> coherenceData =
              new CoherenceQuery<Person>(
                  new CoherenceQueryProvider(CacheFactory.GetCache("dist-Person")));

            var people = from person in coherenceData
                         where person.Title == "Mr"
                         && person.ID.In(new int[] { 1, 6, 9 })
                         select person;

            Person[] Result = people.ToArray();
            Assert.That(Result.Length, Is.EqualTo(2));
            foreach (Person person in Result)
            {
                Assert.That(person.Title, Is.EqualTo("Mr"));
                Assert.That(person.ID, Is.EqualTo(1) | Is.EqualTo(6) | Is.EqualTo(9));
            }
        }

        [Test]
        public void Linq_SelectSomeFieldsFieldsCombinedInFilter()
        {
            AddData();

            CoherenceQuery<Person> coherenceData =
              new CoherenceQuery<Person>(
                  new CoherenceQueryProvider(CacheFactory.GetCache("dist-Person")));

            var people = from person in coherenceData
                         where person.Title == "Mr"
                         && person.ID.In(new int[] { 1, 6, 9 })
                         select new { person.Title, person.ID, person.LastName };

            var Result = people.ToArray();
            Assert.That(Result.Length, Is.EqualTo(2));
            foreach (var person in Result)
            {
                Assert.That(person.ID, Is.EqualTo(1) | Is.EqualTo(6) | Is.EqualTo(9));
                CheckTitle(person.ID, person.Title);
                CheckLastName(person.ID, person.LastName);
            }
        }

        [Test]
        public void Linq_SelectSomeFieldsFieldsFilterExtractor()
        {
            AddData();

            CoherenceQuery<Person> coherenceData =
              new CoherenceQuery<Person>(
                  new CoherenceQueryProvider(CacheFactory.GetCache("dist-Person")));

            var people = from person in coherenceData
                         where person.Extractor<string>("getTitle") == "Mr"
                         select new { person.Title, person.ID, person.LastName };

            var Result = people.ToArray();
            Assert.That(Result.Length, Is.EqualTo(500));
            foreach (var person in Result)
            {                
                CheckTitle(person.ID, person.Title);
                CheckLastName(person.ID, person.LastName);
            }
        }
        #endregion

        #region Person2 Tests
        [Test]
        public void SimplePerson2Query()
        {
            AddData2();

            INamedCache PersonCache = CacheFactory.GetCache("dist-Person2");
            Dictionary<int, Person2> entries = PersonCache.Entries.OfType<ICacheEntry>().ToDictionary<ICacheEntry, int, Person2>(e => (int)e.Key, e => (Person2)e.Value);

            Assert.That(entries, Has.Count(1000), "Invalid number of entries");
            foreach (int key in entries.Keys)
            {
                Person2 p = entries[key];
                CheckFirstName(key, p.FirstName);
                CheckLastName(key, p.LastName);
                CheckAddress(key, p.Address);
                CheckTitle(key, p.Title);
                CheckId(key, p.ID);
            }
        }

        [Test]
        public void Linq_SelectPerson2NoFilter()
        {
            AddData2();

            CoherenceQuery<Person2> coherenceData =
              new CoherenceQuery<Person2>(
                  new CoherenceQueryProvider(CacheFactory.GetCache("dist-Person2")));

            var people = from Person2 in coherenceData
                         select Person2;

            Person2[] Result = people.ToArray();
            Assert.That(Result, Has.Length(1000));
            foreach (Person2 person in Result)
            {
                CheckFirstName(person.ID, person.FirstName);
                CheckLastName(person.ID, person.LastName);
                CheckAddress(person.ID, person.Address);
                CheckTitle(person.ID, person.Title);
            }
        }

        [Test]
        public void Linq_SelectPerson2SimpleFilter()
        {
            AddData2();

            CoherenceQuery<Person2> coherenceData =
              new CoherenceQuery<Person2>(
                  new MetadataCoherenceQueryProvider(CacheFactory.GetCache("dist-Person2")));

            var people = from Person2 in coherenceData
                         where Person2.Title == "Mr"
                         select Person2;

            Person2[] Result = people.ToArray();
            Assert.That(Result, Has.Length(500));
            foreach (Person2 person in Result)
            {
                Assert.That(person.Title, Is.EqualTo("Mr"));
            }
        }

        [Test]
        public void Linq_SelectPerson2CombinedFilter()
        {
            AddData2();

            CoherenceQuery<Person2> coherenceData =
              new CoherenceQuery<Person2>(
                  new MetadataCoherenceQueryProvider(CacheFactory.GetCache("dist-Person2")));

            var people = from Person2 in coherenceData
                         where Person2.Title == "Mr"
                         && Person2.FirstName.Like("First Name 10%")
                         select Person2;

            Person2[] Result = people.ToArray();
            Assert.That(Result, Has.Length(5));

            foreach (Person2 person in Result)
            {
                Assert.That(person.Title, Text.Matches("Mr"));
                Assert.That(person.FirstName, Text.StartsWith("First Name 10"));
            }
        }

        [Test]
        public void Linq_SelectPerson2CombinedInFilter()
        {
            AddData2();

            CoherenceQuery<Person2> coherenceData =
              new CoherenceQuery<Person2>(
                  new MetadataCoherenceQueryProvider(CacheFactory.GetCache("dist-Person2")));

            var people = from Person2 in coherenceData
                         where Person2.Title == "Mr"
                         && Person2.ID.In(new int[] { 1, 6, 9 })
                         select Person2;

            Person2[] Result = people.ToArray();
            Assert.That(Result.Length, Is.EqualTo(2));
            foreach (Person2 person in Result)
            {
                Assert.That(person.Title, Is.EqualTo("Mr"));
                Assert.That(person.ID, Is.EqualTo(1) | Is.EqualTo(6) | Is.EqualTo(9));
            }
        }

        [Test]
        public void Linq_SelectPerson2SomeFieldsFieldsCombinedInFilter()
        {
            AddData2();

            CoherenceQuery<Person2> coherenceData =
              new CoherenceQuery<Person2>(
                  new MetadataCoherenceQueryProvider(CacheFactory.GetCache("dist-Person2")));

            var people = from Person2 in coherenceData
                         where Person2.Title == "Mr"
                         && Person2.ID.In(new int[] { 1, 6, 9 })
                         select new { Person2.Title, Person2.ID, Person2.LastName };

            var Result = people.ToArray();
            Assert.That(Result.Length, Is.EqualTo(2));
            foreach (var person in Result)
            {
                Assert.That(person.ID, Is.EqualTo(1) | Is.EqualTo(6) | Is.EqualTo(9));
                CheckTitle(person.ID, person.Title);
                CheckLastName(person.ID, person.LastName);
            }
        }

        [Test]
        public void Linq_SelectPerson2SomeFieldsFieldsFilterExtractor()
        {
            AddData2();

            CoherenceQuery<Person2> coherenceData =
              new CoherenceQuery<Person2>(
                  new MetadataCoherenceQueryProvider(CacheFactory.GetCache("dist-Person2")));

            var people = from Person2 in coherenceData
                         where Person2.Extractor<string>("getProperty", "Title") == "Mr"
                         select new { Person2.Title, Person2.ID, Person2.LastName };

            var Result = people.ToArray();
            Assert.That(Result.Length, Is.EqualTo(500));
            foreach (var person in Result)
            {
                CheckTitle(person.ID, person.Title);
                CheckLastName(person.ID, person.LastName);
            }
        }
        #endregion
    
        #region Helper Functions
        private void CheckId(int key, int p)
        {
            Assert.That(p, Is.EqualTo(key));
        }

        private void CheckTitle(int key, string title)
        {
            Assert.That(title, Is.EqualTo(key % 2 == 1 ? "Mr" : "Mrs"));
        }

        private void CheckAddress(int key, string address)
        {
            Assert.That(address, Is.EqualTo(string.Format("Address {0}", key)));
        }

        private void CheckLastName(int key, string lastName)
        {
            Assert.That(lastName, Is.EqualTo(string.Format("LastName {0}", key)));
        }

        private void CheckFirstName(int key, string firstName)
        {
            Assert.That(firstName, Is.EqualTo(string.Format("First Name {0}", key)));
        }
        #endregion
    }
}